/*************************************************************
 * this module handles EnOcean telegrams reading, comprising :
 *  - telegram parsing,
 *  - data decoding,
 *  - communication synchronization
 *
 ************************************************************/
var db = require('./database.js');
var fs = require('fs');


/* telegram length from EnOcean specification (in byte) */
var tl = 14;
/* following strings represent available sensor's types */
temperature = "temperature";
occupancy = "occupancy";
contact = "contact";
rockerSwitch = "switch";
availableSensors = [temperature, occupancy, contact, rockerSwitch];

/* time-out used to check the teach-in conf file */
var timeOut = 1;

/*
 * tries to reach the synchronization pattern '0xA55A'
 * in order to extract one valid telegram string.
 *
 * param data : raw 'hex'-encoded string
 * returns : an Object with 2 attributes :
 * 1) tg : the part of data as 'hex'-encoded string 
 *    which is a valid telegram
 * 2) substract : initial data without tg
 * if data does not contain any valid telegram, returns null.
 */
function synchronize(data) {
    var sync = parseInt(data.slice(0, 4), 16);
    //seeks sync pattern
    while (data.length >= tl*2 &&
            parseInt(data.slice(0, 4), 16) != 0xA55A) {
        data = data.substr(4, data.length-4);
    }
    if (data.length < tl*2) {
        return null;
    } else if (parseInt(data.slice(0, 4), 16) == 0xA55A) {
        var toReturn = new Object();
				toReturn.tg = data.substr(0, tl*2);
        toReturn.substract = data.substr(tl*2, data.length-tl*2);
        return toReturn;
    }
}

/* 
 * extracts all fields contained in a 'hex'-encoded raw
 * telegram.
 *
 * param data : raw 'hex'-encoded string
 * returns : if data is valid, an object containing the following attributes :
 * {
 *   org:
 *   data: [] big-endian
 *   id: [] big-endian
 *   status:
 * }
 * To each key corresponds a single (or an array of) byte(s).
 * Else, it returns null.
 */
function parse(data) {
    var sync = parseInt(data.slice(0, 4), 16);
    //checks that data is a valid telegram
    if (data.length == tl*2 && sync == 0xA55A) {
        var telegram = new Object();
        telegram.org = parseInt(data.slice(6, 8), 16);
        telegram.data = [];
        telegram.id = [];
        for (var i = 0; i < 4; i++) {
            telegram.data[i] = parseInt(data.slice(14-2*i, 16-2*i), 16);
            telegram.id[i] = parseInt(data.slice(22-2*i, 24-2*i), 16);
        }
        telegram.status = parseInt(data.slice(24, 26), 16);
        return telegram;
    } else return null;
}

/*
 * defines if client required teachin procedure or not.
 * 
 * param cb : callback with the following signature 'cb(boolean)'
 *
 * returns : boolean
 */
function isTeachIn(cb) {
    fs.exists(__dirname + '/../modules/stats/teachin.json', function(exists) {
		    if (!exists) cb(false);
				else {
						fs.readFile(__dirname + '/../modules/stats/teachin.json', 'utf8', 
							function(err, data) {
								if (err) console.error(err.toString());
								else {
										try {
												var obj = JSON.parse(data);
										} catch (e) {
												cb(false);
												return;
										}
										if (obj.date != undefined) {
												//a time-out is set (cf global at the top of this file)
												var term = new Date(obj.date);
												term.setMinutes(term.getMinutes() + timeOut);
												cb(new Date() < term);
										} else {
												cb(false);
										}
								}
						});
				}
		});
}

/*
 * retrieves the signature of the sensor where telegram comes from.
 *
 * param telegram : an already parsed telegram
 * returns : id org func type desc
 * if telegram.type = contact, user has to push the learn button or close/open the window/door
 * if telegram.type = rockerSwitch, user has to push the A0 button (ie left switch, down)
 */
function decodeTeachIn(telegram, teachin, cb) {	
		var string = ((telegram.data[3] << 8) + telegram.data[2]).toString(2);
		while (string.length < 16) string = '0' + string;
		var typeStr = string.substr(6, 7);
		type = parseInt(typeStr, 2);
		var org = telegram.org;		
		var funcStr = string.substr(0, 6);
		func = parseInt(funcStr, 2);
      		var id = (telegram.id[3] << 8*3) + 
			(telegram.id[2] << 8*2) + 
			(telegram.id[1] << 8) + 
			telegram.id[0];
		var data = new Object();
		data.id = id;
		data.desc = teachin.type;
		data.org = org;
		data.func = func;
		data.type = type;		
		if (teachin.type == temperature && bit(telegram.data[0], 3) == 0 && bit(telegram.data[0], 7) == 1) {
			if(org == 7 && func == 2 && type == 5) {
				cb(data);
			}
		}
		else if (teachin.type == occupancy && bit(telegram.data[0], 3) == 0 && bit(telegram.data[0], 7) == 1) {
			if (org == 7 && func == 8 && type == 1) {
				cb(data);
			}
		}
		else if (teachin.type == contact) {
			if (org == 6 && telegram.data[3] == 0 && telegram.data[2] == 0 && telegram.data[1] == 0) {
				data.func = 0;
				data.type = 1;
				cb(data);
			}
		}
		else if (teachin.type == rockerSwitch) {
			if (org == 5 && telegram.data[3]== 16) {
				data.func = 2;
				data.type = 1;
				cb(data);
			}
		}
}


/*
 * retrieves meaningful data from given telegram.
 *
 * param telegram : an already parsed telegram
 * param cb : signature cb(data)
 * data {
 *      unit
 *      type
 *      value 
 * 	id
 *      }
 * in case of contact sensor, unit = null and value = 0 if open, 1 if closed
 * in case of rockerswitch, unit = null and value = 
 *      - 0 if no button is pressed
 *	- 1 if left button is down
 * 	- 2 if left button is up
 * 	- 3 if right button is down
 * 	- 4 if right button is up
 */
function decode(telegram, cb) {
        var id = (telegram.id[3] << 8*3) + (telegram.id[2] << 8*2) + (telegram.id[1] << 8) + telegram.id[0];
	db.getSensorType(id, function getData (org, func, type) {
	var data = new Object();
		if (org == 7) {
			if (bit(telegram.data[0],3)==1) {
           if (func == 2) {
				        if (type == 5) {
					        
					        data.unit = "°C";
				        	data.type = temperature;
				        	data.value = telegram.data[1];
				        	data.value = 40 - 40*data.value/255;
									data.id = id;
                	cb(data);
			        	}		 
		        }
				else if (func == 8) {
					if (type == 1) {
						data.unit = ["V", "lux"];
						data.type = occupancy;
						data.value = [5.1*telegram.data[3]/255, 1020*telegram.data[2]/255];
						data.id = id; 				
            cb(data);
					}
				}	
                         }
                }
		else if (org == 6) {
			data.type = contact;
			data.unit = null;
			data.value = bit(telegram.data[0],0);
			data.id = id;
      cb(data);
		}
		else if (org == 5) {
			if (func == 2 && type == 1) {
				data.type = rockerSwitch;
				data.id = id;
				data.unit = null;
				if (telegram.data[3] == 0) {
					data.value = 0;
				}
				else {
					 if (bit(telegram.data[3],6) == 0) {
						data.value = 1;
					}
					else {
						data.value = 3;
					}
					if (bit(telegram.data[3],5) == 0) {
						data.value += 1;
					}
				}	
        cb(data);
			}
		}
				});
}

/*
 * returns bit value at specified index in param number
 */
function bit (number, index) {
        var string = number.toString(2);
        return parseInt(string[string.length - 1 - index]);
} 

module.exports.availableSensors = availableSensors;
module.exports.synchronize = synchronize;
module.exports.parse = parse;
module.exports.isTeachIn = isTeachIn;
module.exports.decodeTeachIn = decodeTeachIn;
module.exports.decode = decode;
